/**
 * DragonBonesAnimation Class
 * 
 * A simplified implementation of the DragonBones animation system for
 * managing animation timelines and updating bone transforms.
 */

import { 
  AnimationData,
  AnimationBoneData,
  AnimationSlotData,
  TranslateFrameData,
  RotateFrameData,
  ScaleFrameData,
  DisplayFrameData,
  BoneTimeline,
  SlotTimeline,
  TranslateFrame,
  RotateFrame,
  ScaleFrame,
  DisplayFrame,
  Animation
} from './types';
import { DragonBonesArmature } from './DragonBonesArmature';
import { Logger } from '../../../utils/Logger';

export class DragonBonesAnimation implements Animation {
  public name: string;
  public duration: number;
  public playTimes: number;
  public boneTimelines: Map<string, BoneTimeline> = new Map();
  public slotTimelines: Map<string, SlotTimeline> = new Map();
  public currentTime: number = 0;
  private armature: DragonBonesArmature;
  private _isPlaying: boolean = false;
  private _currentPlayTimes: number = 0;

  constructor(animationData: AnimationData, armature: DragonBonesArmature) {
    this.name = animationData.name;
    this.duration = animationData.duration || 0;
    this.playTimes = animationData.playTimes || 0; // 0 means loop
    this.armature = armature;
    
    const logger = Logger.getInstance();
    logger.log(`Creating animation: ${this.name}`, 'DragonBonesAnimation');
    
    // Create bone timelines
    if (animationData.bone) {
      this.createBoneTimelines(animationData.bone);
    }
    
    // Create slot timelines
    if (animationData.slot) {
      this.createSlotTimelines(animationData.slot);
    }
    
    logger.log(`Successfully created animation: ${this.name}`, 'DragonBonesAnimation');
  }

  /**
   * Creates bone timelines from animation data
   * @param boneDataArray - Array of bone animation data
   */
  private createBoneTimelines(boneDataArray: AnimationBoneData[]): void {
    const logger = Logger.getInstance();
    logger.log(`Creating ${boneDataArray.length} bone timelines`, 'DragonBonesAnimation');
    
    for (const boneData of boneDataArray) {
      const bone = this.armature.bones.get(boneData.name);
      if (!bone) {
        logger.warn(`Bone ${boneData.name} not found for animation timeline`, 'DragonBonesAnimation');
        continue;
      }
      
      const timeline: BoneTimeline = {
        bone: bone,
        translateFrames: [],
        rotateFrames: [],
        scaleFrames: []
      };
      
      // Create translate frames
      if (boneData.translateFrame) {
        timeline.translateFrames = boneData.translateFrame.map(frameData => 
          this.createTranslateFrame(frameData)
        );
      }
      
      // Create rotate frames
      if (boneData.rotateFrame) {
        timeline.rotateFrames = boneData.rotateFrame.map(frameData => 
          this.createRotateFrame(frameData)
        );
      }
      
      // Create scale frames
      if (boneData.scaleFrame) {
        timeline.scaleFrames = boneData.scaleFrame.map(frameData => 
          this.createScaleFrame(frameData)
        );
      }
      
      this.boneTimelines.set(boneData.name, timeline);
      logger.log(`Created bone timeline for: ${boneData.name}`, 'DragonBonesAnimation');
    }
  }

  /**
   * Creates slot timelines from animation data
   * @param slotDataArray - Array of slot animation data
   */
  private createSlotTimelines(slotDataArray: AnimationSlotData[]): void {
    const logger = Logger.getInstance();
    logger.log(`Creating ${slotDataArray.length} slot timelines`, 'DragonBonesAnimation');
    
    for (const slotData of slotDataArray) {
      const slot = this.armature.slots.find(s => s.name === slotData.name);
      if (!slot) {
        logger.warn(`Slot ${slotData.name} not found for animation timeline`, 'DragonBonesAnimation');
        continue;
      }
      
      const timeline: SlotTimeline = {
        slot: slot,
        displayFrames: []
      };
      
      // Create display frames
      if (slotData.displayFrame) {
        timeline.displayFrames = slotData.displayFrame.map(frameData => 
          this.createDisplayFrame(frameData)
        );
      }
      
      this.slotTimelines.set(slotData.name, timeline);
      logger.log(`Created slot timeline for: ${slotData.name}`, 'DragonBonesAnimation');
    }
  }

  /**
   * Creates a translate frame from frame data
   * @param frameData - The frame data
   * @returns A translate frame
   */
  private createTranslateFrame(frameData: TranslateFrameData): TranslateFrame {
    return {
      duration: frameData.duration || 0,
      position: {
        x: frameData.x || 0,
        y: frameData.y || 0
      }
    };
  }

  /**
   * Creates a rotate frame from frame data
   * @param frameData - The frame data
   * @returns A rotate frame
   */
  private createRotateFrame(frameData: RotateFrameData): RotateFrame {
    return {
      duration: frameData.duration || 0,
      rotation: frameData.rotate || 0
    };
  }

  /**
   * Creates a scale frame from frame data
   * @param frameData - The frame data
   * @returns A scale frame
   */
  private createScaleFrame(frameData: ScaleFrameData): ScaleFrame {
    return {
      duration: frameData.duration || 0,
      scale: {
        x: frameData.x || 1,
        y: frameData.y || 1
      }
    };
  }

  /**
   * Creates a display frame from frame data
   * @param frameData - The frame data
   * @returns A display frame
   */
  private createDisplayFrame(frameData: DisplayFrameData): DisplayFrame {
    return {
      duration: frameData.duration || 0,
      displayIndex: frameData.value || 0
    };
  }

  /**
   * Resets the animation to the beginning
   */
  public reset(): void {
    this.currentTime = 0;
    this._isPlaying = true;
    this._currentPlayTimes = 0;
  }

  /**
   * Advances the animation time
   * @param deltaTime - Time elapsed since last frame
   */
  public advanceTime(deltaTime: number): void {
    if (!this._isPlaying) {
      return;
    }
    
    this.currentTime += deltaTime;
    
    // Check if animation has completed
    if (this.duration > 0) {
      if (this.playTimes > 0 && this.currentTime >= this.duration * this.playTimes) {
        // Animation completed
        this.currentTime = this.duration * this.playTimes;
        this._isPlaying = false;
      } else if (this.playTimes === 0) {
        // Looping animation
        this.currentTime %= this.duration;
      }
    }
    
    // Update bone transforms based on timelines
    this.updateBoneTransforms();
    
    // Update slot displays based on timelines
    this.updateSlotDisplays();
  }

  /**
   * Updates bone transforms based on animation timelines
   */
  private updateBoneTransforms(): void {
    for (const [boneName, timeline] of this.boneTimelines.entries()) {
      const bone = timeline.bone;
      if (!bone) continue;
      
      // For simplicity, we'll just use the first frame of each type
      // In a full implementation, we would interpolate between frames
      if (timeline.translateFrames.length > 0) {
        const frame = timeline.translateFrames[0];
        bone.transform.x = frame.position.x;
        bone.transform.y = frame.position.y;
      }
      
      if (timeline.rotateFrames.length > 0) {
        const frame = timeline.rotateFrames[0];
        bone.transform.rotation = frame.rotation;
      }
      
      if (timeline.scaleFrames.length > 0) {
        const frame = timeline.scaleFrames[0];
        bone.transform.scaleX = frame.scale.x;
        bone.transform.scaleY = frame.scale.y;
      }
    }
    
    // Update global transforms
    this.armature.updateTransforms();
  }

  /**
   * Updates slot displays based on animation timelines
   */
  private updateSlotDisplays(): void {
    for (const [slotName, timeline] of this.slotTimelines.entries()) {
      const slot = timeline.slot;
      if (!slot) continue;
      
      // For simplicity, we'll just use the first frame
      // In a full implementation, we would handle frame changes over time
      if (timeline.displayFrames.length > 0) {
        const frame = timeline.displayFrames[0];
        if (frame.displayIndex >= 0 && frame.displayIndex < slot.displays.length) {
          slot.display = slot.displays[frame.displayIndex];
        }
      }
    }
  }

  /**
   * Gets whether the animation is currently playing
   */
  public get isPlaying(): boolean {
    return this._isPlaying;
  }
}